package project2;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;

import misc.not_Legal_Exception;
import evolution_superclasses.Phenotype;

public class spiking_neuron_Fitness_Evaluation{
	private spiking_neuron_phenotype _target;
	private int _methodNr;

	public spiking_neuron_Fitness_Evaluation(int i, int nr) throws IOException, not_Legal_Exception{
		setTarget(i);
		_methodNr = nr;
		if(nr < 0 || nr > 2)throw new not_Legal_Exception("No such fitness method!");
	}
	
	public void setTarget(int i) throws IOException{
		BufferedReader br = new BufferedReader(new FileReader("Proj2_Targets/training data/izzy-train"+i+".dat"));
		double[] train = new double[1001];
		try {
	    	int count = 0;
	        String[] line = br.readLine().split(" ");
	        for(String s:line){
	        	if(s.trim().equals(""))continue;//As the targets do not have same format. (nr 2 starts with space)
	        	train[count++] = Double.parseDouble(s);
	        }
	    } finally {
	        br.close();
	    }
	    _target = new spiking_neuron_phenotype(null);
		_target.set_spikeTrain(train);
		_target.setSpikeTimeTrain();
	}

	public double evaluateFitness(Phenotype p) throws not_Legal_Exception{
		double fitness = 0;
		switch (_methodNr) {
			case 0:
				fitness = 100/spikeTimeDistanceMetric(p);
				break;
			case 1:
				fitness = 100/spikeTimeIntervalMetric(p);
				break;
			case 2:
				fitness = 1/WaveFormDistanceMetric(p);
				break;
			default:
				throw new not_Legal_Exception("No such fitness method!");
		}
		return fitness;
	}
	
	public double spikeTimeDistanceMetric(Phenotype p){
		ArrayList<Integer> list1 = _target.get_spikeTimeTrain();
		ArrayList<Integer> list2 = ((spiking_neuron_phenotype)p).get_spikeTimeTrain();
		
		int N = list1.size(), M = list2.size();
		if(N > M){
			N = list2.size();
			M = list1.size();
		}
		double penalty = N==0?(M-N)*_target.get_spikeTrain().length:(M-N)*_target.get_spikeTrain().length/(2*N);
		double similarity = 0;
		
		//assume p is 2, can therefore drop the absolute value, and use sqrt at the end.
		for(int i = 0; i < N; i++){
			similarity += Math.pow(list1.get(i)-list2.get(i), 2);
		}
		double result = N==0?penalty:(Math.sqrt(similarity)+penalty)/N;
		result = result>=10000?10000:result;
		return result<=0.01?0.01:result;
	}
	
	public double spikeTimeIntervalMetric(Phenotype p){
		ArrayList<Integer> list1 = _target.get_spikeTimeTrain();
		ArrayList<Integer> list2 = ((spiking_neuron_phenotype)p).get_spikeTimeTrain();
		
		int N = list1.size(), M = list2.size();
		if(N > M){
			N = list2.size();
			M = list1.size();
		}
		double penalty = N==0?(M-N)*_target.get_spikeTrain().length:(M-N)*_target.get_spikeTrain().length/(2*N);
		double similarity = 0;
		
		//assume p is 2, can therefore drop the absolute value, and use sqrt at the end.
		for(int i = 1; i < N; i++){
			similarity += Math.pow(((list1.get(i)-list1.get(i-1))-(list2.get(i)-list2.get(i-1))), 2);
		}

		double result = N==0?penalty:(Math.sqrt(similarity)+penalty)/N;
		result = result>=10000?10000:result;
		return result<=0.01?0.01:result;
	}
	
	public double WaveFormDistanceMetric(Phenotype p){
		double[] list1 = _target.get_spikeTrain();
		double[] list2 = ((spiking_neuron_phenotype)p).get_spikeTrain();
		
		int N = list1.length;
		double similarity = 0;
		
		//assume p is 2, can therefore drop the absolute value, and use sqrt at the end.
		for(int i = 0; i < N; i++){
			similarity += Math.pow(list1[i]-list2[i], 2);
		}
		
		double result = Math.sqrt(similarity)/N;
		result = result>=1000?1000:result;
		return result<=0.01?0.01:result;
	}
}
